﻿//ExcelStrategyReport.cs
//Copyright (c) 2013 StockSharp LLC, all rights reserved.
//This code module is part of StockSharp library.
//This code is licensed under the GNU GENERAL PUBLIC LICENSE Version 3.
//See the file License.txt for the license details.
//More info on: http://stocksharp.com

namespace StockSharp.Algo.Reporting
{
	using System;
	using System.Collections.Generic;
	using System.Linq;
	using System.Windows.Media;

	using Ecng.Common;
	using Ecng.ComponentModel;
	using Ecng.Interop;
	using Ecng.Collections;

	using StockSharp.Algo.PnL;
	using StockSharp.Algo.Strategies;
	using StockSharp.BusinessEntities;

	/// <summary>
	/// Генератор отчета для стратегии в формате Excel.
	/// </summary>
	public class ExcelStrategyReport : StrategyReport
	{
		/// <summary>
		/// Создать генератор Excel отчетов.
		/// </summary>
		/// <param name="strategy">Стратегия, для которой необходимо сгенерировать отчет.</param>
		/// <param name="fileName">Название файла, в котором сгенерируется отчет в формате Excel.</param>
		public ExcelStrategyReport(Strategy strategy, string fileName)
			: base(strategy)
		{
			if (fileName.IsEmpty())
				throw new ArgumentNullException("fileName");

			FileName = fileName;
			SheetName = "Отчет по " + strategy;
			ExcelVersion = 2007;
			IncludeOrders = true;
			Decimals = 2;
		}

		/// <summary>
		/// Создать генератор Excel отчетов.
		/// </summary>
		/// <param name="strategy">Стратегия, для которой необходимо сгенерировать отчет.</param>
		/// <param name="fileName">Название файла, в котором сгенерируется отчет в формате Excel.</param>
		/// <param name="template">Файл-шаблон, который будет скопирован в <see cref="FileName"/> и дозаполнен листом <see cref="SheetName"/>.</param>
		public ExcelStrategyReport(Strategy strategy, string fileName, string template)
			: this(strategy, fileName)
		{
			if (template.IsEmpty())
				throw new ArgumentNullException("template");

			Template = template;
		}

		/// <summary>
		/// Создать генератор Excel отчетов.
		/// </summary>
		/// <param name="strategy">Стратегия, для которой необходимо сгенерировать отчет.</param>
		/// <param name="fileName">Название файла, в котором сгенерируется отчет в формате Excel.</param>
		/// <param name="template">Файл-шаблон, который будет скопирован в <see cref="FileName"/> и дозаполнен листом <see cref="SheetName"/>.</param>
		/// <param name="sheetName">Названия листа, в который будет записан отчет.</param>
		public ExcelStrategyReport(Strategy strategy, string fileName, string template, string sheetName)
			: this(strategy, fileName, template)
		{
			if (sheetName.IsEmpty())
				throw new ArgumentNullException("sheetName");

			SheetName = sheetName;
		}

		/// <summary>
		/// Название файла, в котором сгенерируется отчет в формате Excel.
		/// </summary>
		public string FileName { get; private set; }

		/// <summary>
		/// Файл-шаблон, который будет скопирован в <see cref="FileName"/> и дозаполнен листами Strategy, Orders и Trades.
		/// </summary>
		public string Template { get; private set; }

		/// <summary>
		/// Названия листа, в который будет записан отчет.
		/// </summary>
		public string SheetName { get; private set; }

		/// <summary>
		/// Версия Excel-я. Влияет на максимальное количество строчек. По-умолчанию установлено значение 2007.
		/// </summary>
		public int ExcelVersion { get; set; }

		/// <summary>
		/// Добавлять в отчет заявки. По-умолчанию заявки добавляются.
		/// </summary>
		public bool IncludeOrders { get; set; }

		/// <summary>
		/// Количество знаков после запятой. По-умолчанию равно 2.
		/// </summary>
		public int Decimals { get; set; }

		/// <summary>
		/// Сгенерировать отчет в формате Excel.
		/// </summary>
		public override void Generate()
		{
			var hasTemplate = Template.IsEmpty();

			using (var worker = hasTemplate ? new ExcelWorker() : new ExcelWorker(Template))
			{
				if (Template.IsEmpty())
				{
					worker.RenameSheet(SheetName);
				}
				else
				{
					if (worker.ContainsSheet(SheetName))
						worker.AddSheet(SheetName);
				}

				worker
					.SetCell(0, 0, "Общая информация по стратегии:")

					.SetCell(0, 1, "Код инструмента:")
					.SetCell(1, 1, Strategy.Security != null ? Strategy.Security.Code : string.Empty)

					.SetCell(0, 2, "Портфель:")
					.SetCell(1, 2, Strategy.Portfolio != null ? Strategy.Portfolio.Name : string.Empty)

					.SetCell(0, 3, "Общее время работы:")
					.SetCell(1, 3, Format(Strategy.TotalWorkingTime))

					.SetCell(0, 4, "Общее время исполнения:")
					//.SetCell(1, 4, FormatTime(base.Strategy.TotalCPUTime))

					.SetCell(0, 5, "Позиция:")
					.SetCell(1, 5, Strategy.Position)

					.SetCell(0, 6, "P&L:")
					.SetCell(1, 6, Strategy.PnL)

					.SetCell(0, 7, "Комиссия:")
					.SetCell(1, 7, Strategy.Commission)

					.SetCell(0, 8, "Проскальзывание:")
					.SetCell(1, 8, Strategy.Slippage)

					.SetCell(0, 9, "Задержка:")
					.SetCell(1, 9, Format(Strategy.Latency));

				var rowIndex = 11;

				foreach (var parameter in Strategy.StatisticManager.Parameters.SyncGet(c => c.ToArray()))
				{
					var value = parameter.Value;

					if (value is TimeSpan)
						value = Format((TimeSpan)value);
					else if (value is decimal)
						value = MathHelper.Round((decimal)value, Decimals);

					worker
						.SetCell(0, rowIndex, parameter.Name)
						.SetCell(1, rowIndex, value);

					rowIndex++;
				}

				rowIndex += 2;
				worker.SetCell(0, rowIndex, "Параметры стратегии:");
				rowIndex++;

				foreach (var strategyParam in Strategy.Parameters.SyncGet(c => c.ToArray()))
				{
					var value = strategyParam.Value;

					if (value is TimeSpan)
						value = Format((TimeSpan)value);
					else if (value is decimal)
						value = MathHelper.Round((decimal)value, Decimals);

					worker
						.SetCell(0, rowIndex, strategyParam.Name)
						.SetCell(1, rowIndex, value);

					rowIndex++;
				}

				rowIndex += 2;
				worker.SetCell(0, rowIndex, "Комиссия по типам:");
				rowIndex++;

				foreach (var pair in Strategy.CommissionManager.CommissionPerTypes.SyncGet(d => d.ToArray()))
				{
					if (pair.Value == 0)
						continue;

					worker
						.SetCell(0, rowIndex, pair.Key)
						.SetCell(1, rowIndex, pair.Value);

					rowIndex++;
				}

				var columnShift = 3;

				worker
					.SetCell(columnShift + 0, 0, "Сделки")

					.SetCell(columnShift + 0, 1, "Номер сделки").SetStyle(columnShift + 0, typeof(long))
					.SetCell(columnShift + 1, 1, "Номер транзакции").SetStyle(columnShift + 1, typeof(long))
					.SetCell(columnShift + 2, 1, "Время").SetStyle(columnShift + 2, "HH:mm:ss.fff")
					.SetCell(columnShift + 3, 1, "Цена").SetStyle(columnShift + 3, typeof(decimal))
					.SetCell(columnShift + 4, 1, "Цена заявки").SetStyle(columnShift + 4, typeof(decimal))
					.SetCell(columnShift + 5, 1, "Объем").SetStyle(columnShift + 5, typeof(decimal))
					.SetCell(columnShift + 6, 1, "Направление")
					.SetCell(columnShift + 7, 1, "Номер заявки").SetStyle(columnShift + 7, typeof(long))
					.SetCell(columnShift + 8, 1, "Проскальзывание").SetStyle(columnShift + 8, typeof(decimal))
					.SetCell(columnShift + 9, 1, "Проскальзывание (котирование)").SetStyle(columnShift + 9, typeof(decimal))
					.SetCell(columnShift + 10, 1, "Комментарий")
					.SetCell(columnShift + 11, 1, "Прибыль в рублях").SetStyle(columnShift + 11, typeof(decimal))
					.SetCell(columnShift + 12, 1, "Прибыль в пунктах").SetStyle(columnShift + 12, typeof(decimal))
					.SetCell(columnShift + 13, 1, "Прибыль в рублях (суммарная)").SetStyle(columnShift + 13, typeof(decimal))
					.SetCell(columnShift + 14, 1, "Прибыль в пунктах (суммарная)").SetStyle(columnShift + 14, typeof(decimal))
					.SetCell(columnShift + 15, 1, "Позиция").SetStyle(columnShift + 15, typeof(decimal));

				worker
					.SetConditionalFormatting(columnShift + 11, ComparisonOperator.Less, "0", null, Colors.Red)
					.SetConditionalFormatting(columnShift + 12, ComparisonOperator.Less, "0", null, Colors.Red)
					.SetConditionalFormatting(columnShift + 13, ComparisonOperator.Less, "0", null, Colors.Red)
					.SetConditionalFormatting(columnShift + 14, ComparisonOperator.Less, "0", null, Colors.Red);

				var totalPnL = 0m;
				var position = 0m;

				var queues = new Dictionary<Security, PnLQueue>();

				rowIndex = 2;
				foreach (var trade in Strategy.MyTrades.ToArray())
				{
					var info = Strategy.PnLManager.ProcessMyTrade(trade);

					totalPnL += info.PnL;
					position += trade.GetPosition();

					var queue = queues.SafeAdd(trade.Trade.Security, key => new PnLQueue(key));

					var localInfo = queue.Process(trade, false);

					worker
						.SetCell(columnShift + 0, rowIndex, trade.Trade.Id)
						.SetCell(columnShift + 1, rowIndex, trade.Order.TransactionId)
						.SetCell(columnShift + 2, rowIndex, Format(trade.Trade.Time))
						.SetCell(columnShift + 3, rowIndex, trade.Trade.Price)
						.SetCell(columnShift + 4, rowIndex, trade.Order.Price)
						.SetCell(columnShift + 5, rowIndex, trade.Trade.Volume)
						.SetCell(columnShift + 6, rowIndex, Format(trade.Order.Direction))
						.SetCell(columnShift + 7, rowIndex, trade.Order.Id)
						.SetCell(columnShift + 8, rowIndex, trade.GetSlippage())
						.SetCell(columnShift + 9, rowIndex, Strategy.SlippageManager.GetSlippage(trade))
						.SetCell(columnShift + 10, rowIndex, trade.Order.Comment)
						.SetCell(columnShift + 11, rowIndex, MathHelper.Round(info.PnL, Decimals))
						.SetCell(columnShift + 12, rowIndex, MathHelper.Round(localInfo.PnL, Decimals))
						.SetCell(columnShift + 13, rowIndex, MathHelper.Round(totalPnL, Decimals))
						.SetCell(columnShift + 14, rowIndex, MathHelper.Round(queue.RealizedPnL, Decimals))
						.SetCell(columnShift + 15, rowIndex, position);

					rowIndex++;
				}

				if (IncludeOrders)
				{
					columnShift += 17;

					worker
						.SetCell(columnShift + 0, 0, "Заявки")

						.SetCell(columnShift + 0, 1, "Номер заявки").SetStyle(columnShift + 0, typeof(long))
						.SetCell(columnShift + 1, 1, "Номер транзакции").SetStyle(columnShift + 1, typeof(long))
						.SetCell(columnShift + 2, 1, "Направление")
						.SetCell(columnShift + 3, 1, "Зарегистрирована").SetStyle(columnShift + 3, "HH:mm:ss.fff")
						.SetCell(columnShift + 4, 1, "Изменена").SetStyle(columnShift + 4, "HH:mm:ss.fff")
						.SetCell(columnShift + 5, 1, "Продолжительность")
						.SetCell(columnShift + 6, 1, "Цена").SetStyle(columnShift + 6, typeof(decimal))
						.SetCell(columnShift + 7, 1, "Цена (усредн.)").SetStyle(columnShift + 7, typeof(decimal))
						.SetCell(columnShift + 8, 1, "Статус")
						.SetCell(columnShift + 9, 1, "Состояние")
						.SetCell(columnShift + 10, 1, "Баланс").SetStyle(columnShift + 10, typeof(decimal))
						.SetCell(columnShift + 11, 1, "Объем").SetStyle(columnShift + 11, typeof(decimal))
						.SetCell(columnShift + 12, 1, "Тип")
						.SetCell(columnShift + 13, 1, "Проскальзывание").SetStyle(columnShift + 13, typeof(decimal))
						.SetCell(columnShift + 14, 1, "Задержка регистрации")
						.SetCell(columnShift + 15, 1, "Задержка отмены")
						.SetCell(columnShift + 16, 1, "Комментарий");

					worker
						.SetConditionalFormatting(columnShift + 9, ComparisonOperator.Equal, "\"Исполнена\"", null, Colors.Green)
						.SetConditionalFormatting(columnShift + 9, ComparisonOperator.Equal, "\"Активна\"", null, Colors.Red);

					rowIndex = 2;
					foreach (var order in Strategy.Orders.ToArray())
					{
						worker
							.SetCell(columnShift + 0, rowIndex, order.Id)
							.SetCell(columnShift + 1, rowIndex, order.TransactionId)
							.SetCell(columnShift + 2, rowIndex, Format(order.Direction))
							.SetCell(columnShift + 3, rowIndex, Format(order.Time))
							.SetCell(columnShift + 4, rowIndex, Format(order.LastChangeTime))
							.SetCell(columnShift + 5, rowIndex, Format(order.LastChangeTime - order.Time))
							.SetCell(columnShift + 6, rowIndex, order.Price)
							.SetCell(columnShift + 7, rowIndex, MathHelper.Round(order.GetAveragePrice(), Decimals))
							.SetCell(columnShift + 8, rowIndex, Format(order.State))
							.SetCell(columnShift + 9, rowIndex, order.IsMatched() ? "Исполнена" : (order.IsCanceled() ? "Отменена" : "Активна"))
							.SetCell(columnShift + 10, rowIndex, order.Balance)
							.SetCell(columnShift + 11, rowIndex, order.Volume)
							.SetCell(columnShift + 12, rowIndex, Format(order.Type))
							.SetCell(columnShift + 13, rowIndex, Strategy.SlippageManager.GetSlippage(order))
							.SetCell(columnShift + 14, rowIndex, Format(order.LatencyRegistration))
							.SetCell(columnShift + 15, rowIndex, Format(order.LatencyCancellation))
							.SetCell(columnShift + 16, rowIndex, order.Comment);

						rowIndex++;
					}

					var stopOrders = Strategy.StopOrders.ToArray();

					if (stopOrders.Length > 0)
					{
						rowIndex += 2;

						worker
							.SetCell(columnShift + 0, rowIndex - 1, "Стоп-Заявки")

							.SetCell(columnShift + 0, rowIndex, "Номер заявки").SetStyle(columnShift + 0, typeof(long))
							.SetCell(columnShift + 1, rowIndex, "Номер транзакции").SetStyle(columnShift + 1, typeof(long))
							.SetCell(columnShift + 2, rowIndex, "Направление")
							.SetCell(columnShift + 3, rowIndex, "Время").SetStyle(columnShift + 3, "HH:mm:ss.fff")
							.SetCell(columnShift + 4, rowIndex, "Цена").SetStyle(columnShift + 4, typeof(decimal))
							.SetCell(columnShift + 5, rowIndex, "Статус")
							.SetCell(columnShift + 6, rowIndex, "Состояние")
							.SetCell(columnShift + 7, rowIndex, "Объем").SetStyle(columnShift + 7, typeof(decimal))
							.SetCell(columnShift + 8, rowIndex, "Задержка регистрации")
							.SetCell(columnShift + 9, rowIndex, "Задержка отмены")
							.SetCell(columnShift + 10, rowIndex, "Номер производной заявки").SetStyle(columnShift + 9, typeof(long));

						var stopParams = stopOrders[0].Condition.Parameters.Keys.ToArray();

						for (var i = 0; i < stopParams.Length; i++)
							worker.SetCell(columnShift + 11 + i, rowIndex, stopParams[i]);

						foreach (var order in stopOrders)
						{
							worker
								.SetCell(columnShift + 0, rowIndex, order.Id)
								.SetCell(columnShift + 1, rowIndex, order.TransactionId)
								.SetCell(columnShift + 2, rowIndex, Format(order.Direction))
								.SetCell(columnShift + 3, rowIndex, Format(order.Time))
								.SetCell(columnShift + 4, rowIndex, order.Price)
								.SetCell(columnShift + 5, rowIndex, Format(order.State))
								.SetCell(columnShift + 6, rowIndex, order.IsMatched() ? "Исполнена" : (order.IsCanceled() ? "Отменена" : string.Empty))
								.SetCell(columnShift + 7, rowIndex, order.Volume)
								.SetCell(columnShift + 8, rowIndex, Format(order.LatencyRegistration))
								.SetCell(columnShift + 9, rowIndex, Format(order.LatencyCancellation))
								.SetCell(columnShift + 10, rowIndex, order.DerivedOrder != null ? (object)order.DerivedOrder.Id : string.Empty);

							for (var i = 0; i < stopParams.Length; i++)
								worker.SetCell(columnShift + 11 + i, rowIndex, order.Condition.Parameters[stopParams[i]] ?? string.Empty);

							rowIndex++;
						}
					}
				}

				worker.Save(FileName);
			}
		}
	}
}